#include "Database.h"
#include "Utilities.h"
#include "sqlite3.h"
#include <iostream>
#include <cstdlib>

#define INIT_SQL_STMT 11
std::string init_sql [INIT_SQL_STMT] =
{
	"begin transaction;"

	,"create table StrTab("						//String table,used for string dedup to save space.
		"value string primary key,"				//Actual string
		"type integer,"							//Type of string , 0:Executable , 1:Directory , 2:Arguement
		"uid integer unique not null);"			//This id is used in other table to refer to the value.

	,"create table SesTab("						//Session table,contains all active and/or persistant sessions.(NOTE:NOT IMPLEMENTED)
		"id integer primary key,"
		"name string,"							//User defined session name.
		"active integer,"						//If set session is attached to a vash instance.
		"type integer);"						//Can be 0:Normal , 1:Persistant , 2:Automatic.

	,"create table HistTab("					//History table.
		"id integer primary key autoincrement,"	//Time stamp.
		"exec integer,"							//Integer pointing to StrTab entry containing executable name(eg ls).
		"args string,"							//Arguements of executable.
		"ses_id integer,"						//Session that issued this command.(NOTE:NOT YET USED)
		"dir_id integer);"						//Integer pointing to StrTab entry containing cwd of command(eg /home/user).

	,"create table PathTable("					//Table containing directories of PATH
		"pid integer primary key,"				//Integer pointing to StrTab entry containing directory path(eg /bin).
		"last_scan string);"					//Time stamp of last scan.

	,"create table ExecList("					//Table containing all executables in PATH
		"name integer not null,"				//Executable name eg" 'xterm'
		"path integer not null,"				//Same as PathTable.pid.
		"lfu integer default(0),"				// LFU:Frequency,used for autocomplete.
		"primary key(name,path));"

		/*
	,"create table UiConfTab("					//configuration for the Ui.
		"max_inpt_h integer default(10),"		//Maximum height of the input window.
		"max_auto_comp integer default(30),"	//Number of autocomplete recomendations.
		"word_wrap integer);"					//Word wraping
	,"create table DirConf("					//Directory specific settings.
		"id integer primary key,"				//same as HistTab.dir_id
		"hsize integer);"						//History size,overides VashConf.hsize.
	,"create table VashConf("					//Vash configuration table.
		"hsize integer,"						//Directory specific history size in bytes for entire session.
		"daemon_kill_idle,"						//Time in seconds after which idle daemon(no active sessions) is terminated.
		"retain_detached);"						//Time in seconds to retain detached sessions.
	,"create table SesConfTab("					//Session configuration table for all active and persistant sessions.(NOTE:NOT IMPLEMENTED)
		"id primary key,"						//Same as SesTab.id .
		"start_path integer,"					//Integer pointing to StrTab entry containing start path of session.
		"start_last boolean"					//If set then start_path will be the equall to the path of the last command run in the session.
		"hsize integer);"						//History size in bytes for entire session.
*/
	,"create table VashConfTab("				//Vash Related Configuration Table(Key,Value)
		"key string primary key,"				//Key
		"value string);"						//Value

	,"create table DirConfTab("					//Directory Related Configuration Table(dir,Key,Value)
		"dir integer,"							//Directory ID
		"key integer,"							//key id
		"value string);"						//Value

	//Add Default Values
	,"insert into VashConfTab (key,value) values(\"max_auto_comp\",\"100\");"
	,"insert into VashConfTab (key,value) values(\"max_inpt_h\",\"10\");"

	,"commit transaction;"
};

Database :: Database(std::string config_path)
{
	int ret;
	int stmt;

	if(!createPath(config_path))
	{
		std::cerr << "Could not use \'" << config_path << "\'!" << std::endl;
	}
	ret = sqlite3_open_v2((config_path+"/db.sql").c_str(),&db,SQLITE_OPEN_READWRITE,0);
	if(ret != SQLITE_OK)
		std::cerr << "Could not open \'" << (config_path+"/db.sql") << "\': " << sqlite3_errmsg(db) << "!" << std::endl;
	else
	{
		std::cerr << "Succesfully opened \'" << (config_path+"/db.sql") << "\'" << std::endl;
		return ;
	}
	std::cerr << "Creting database \'" << (config_path+"/db.sql") << "\'." << std::endl;
	ret = sqlite3_open((config_path+"/db.sql").c_str(),&db);
	if(ret != SQLITE_OK)
	{
		std::cerr << sqlite3_errmsg(db) << "!" << std::endl;
		std::cerr << "Could not create vash database at \'" << (config_path+"/db.sql") << "\'." << std::endl;
		return ;
	}
	else
	{
		std::cerr << "Succesfully created \'" << (config_path+"/db.sql") << "\'" << std::endl;
		std::cerr << "Succesfully opened \'" << (config_path+"/db.sql") << "\'" << std::endl;
	}

	for(stmt = 0 ; stmt < INIT_SQL_STMT ; stmt++)
	{
		if(!execStatement(init_sql[stmt]))
			break;
	}
	if(stmt == INIT_SQL_STMT)
		std::cerr << "Succesfully initialized vash db at \'" << (config_path+"/db.sql") << "\'" << std::endl;
	else
		std::cerr << "Initialization of vash database at \'" << (config_path+"/db.sql") << "\' failed!" << std::endl;
}

void Database :: vacuum()
{
	execStatement("vacuum;");
}

Database :: ~Database()
{
	sqlite3_close_v2(db);
}

bool Database :: execStatement(std::string command)
{
	char * err;
	sqlite3_exec(db,command.c_str(),0,0,&err);
	if(err)
		std::cerr << "Error:" << err << " at command: " << command << std::endl;
	sqlite3_free(err);
	return !err;
}

int unnaryIntCB(void * data,int argc,char** argv,char** columns)
{
	int * ret = (int*)data;
	if(argv[0])
		*ret = atoi(argv[0]);
	else
		*ret = 0;
	return 0;
}

int unnaryStrCB(void * data,int argc,char** argv,char** columns)
{
	std::string * ret = (std::string*)data;
	if(argv[0])
	{
		std::string tmp = argv[0];
		*ret = tmp;
	}
	else
		*ret = "(NULL)";
	return 0;
}

bool Database :: execUnarryIntStatement(std::string command,int & value)
{
	char * err;
	int ret;
	sqlite3_exec(db,command.c_str(),unnaryIntCB,&ret,&err);
	if(err)
		std::cerr << "Error:" << err << " at command: " << command << std::endl;
	sqlite3_free(err);
	value = ret;
	return !err;
}

bool Database :: execUnarryStrStatement(std::string command,std::string & value)
{
	char * err;
	std::string ret = "(NULL)";
	sqlite3_exec(db,command.c_str(),unnaryStrCB,&ret,&err);
	if(err)
		std::cerr << "Error:" << err << " at command: " << command << std::endl;

	sqlite3_free(err);
	value = ret;
	return !err;
}

int Database :: getConfIntValue(std::string table,std::string expr)
{
	int result = 0;
	std::string cmd = "select value ";
	cmd += " from ";
	cmd += table + "ConfTab where ";
	cmd += expr;
	execUnarryIntStatement(cmd,result);
	return result;
}

int makeQueryCB(void * data,int argc,char** argv,char** columns)
{
	ValueTable * ret = (ValueTable*)data;
	if(ret->size() != (size_t)argc)
	{
		ret->resize(argc);
		for( int cnt = 0 ; cnt < argc ; cnt++)
			(*ret)[cnt].first = columns[cnt];
	}
	for( int cnt = 0 ; cnt < argc ; cnt++)
	{
		if(argv[cnt])
			(*ret)[cnt].second.push_back(argv[cnt]);
		else
			(*ret)[cnt].second.push_back(std::string());
	}
	return 0;
}

ValueTable * Database :: makeQuery(std::string cmd)
{
	ValueTable * ret;
	ret = new ValueTable();
	char * err = 0;
//	std::cerr << "CMD(" << cmd << ")\n";
	sqlite3_exec(db,cmd.c_str(),makeQueryCB,ret,&err);
	if(err)
		std::cerr << "Error:" << err << " at command: " << cmd << "\n";
	if(ret->size() == 0)
	{
		sqlite3_stmt * stmt;
		sqlite3_prepare(db,cmd.c_str(),cmd.size(),&stmt,0);
		std::pair<std::string,std::vector<std::string> > pair;
		for(int cnt = 0 ; cnt < sqlite3_column_count(stmt) ; cnt++)
		{
			pair.first = sqlite3_column_name(stmt,cnt);
			ret->push_back(pair);
		}
		sqlite3_finalize(stmt);
	}
	return ret;
}


std::string Database :: addString(std::string str)
{
	std::string val;
	execUnarryStrStatement(
		"insert or ignore into StrTab (value,uid) values (\""+str+"\",(select count(uid) from StrTab));"
		"select uid from StrTab where value==\""+str+"\";",val);
	return val;
}